<?php
/**
 * Imagecow PHP library
 *
 * Abstract core class extended by one of the available libraries (GD, Imagick)
 * Original code from phpCan Image class (http://idc.anavallasuiza.com/)
 *
 * PHP version 5.3
 *
 * @author Oscar Otero <http://oscarotero.com> <oom@oscarotero.com>
 * @license GNU Affero GPL version 3. http://www.gnu.org/licenses/agpl-3.0.html
 * @version 0.4 (2012)
 */

namespace Imagecow;

define('IMAGECOW_ERROR_LOADING', 1);
define('IMAGECOW_ERROR_FUNCTION', 2);
define('IMAGECOW_ERROR_INPUT', 3);

use Imagecow\ImageException;

abstract class Image {
	protected $image;
	protected $quality = 90;
	protected $Error;

	
	/**
	 * Static function to create a new Imagecow instance
	 *
	 * @param string  $library  The name of the image library to use (Gd or Imagick). If it's not defined, detects automatically the library to use.
	 *
	 * @return object  The Imagecow instance
	 *
	 * @throws Exception if the image library does not exists.
	 */
	static function create ($library = null) {
		if (!$library) {
			$library = extension_loaded('imagick') ? 'Imagick' : 'Gd';
		}

		$class = 'Imagecow\\Libs\\'.$library;

		if (class_exists($class)) {
			return new $class;
		}

		throw new \Exception('The image library is not valid');
	}



	/**
	 * Static function to filter the transform operations according to client properties
	 * Useful to generate responsive images
	 *
	 * @param string  $client_properties  The cookie value generated by Imagecow.js scripts with the client dimmensions.
	 * @param string  $operations         The operations to transform the image
	 *
	 * @return string  The operations that matches with the client properties.
	 */
	static function getResponsiveOperations ($client_properties, $operations = '') {
		if (!$operations) {
			return '';
		}

		$client = array();

		foreach (explode('|', str_replace(' ', '', $client_properties)) as $client_properties) {
			$client_properties = explode(',', $client_properties);
			$client[array_shift($client_properties)] = $client_properties;
		}

		$width = isset($client['dimensions'][0]) ? intval($client['dimensions'][0]) : null;
		$height = isset($client['dimensions'][1]) ? intval($client['dimensions'][1]) : null;

		$transform = array();

		foreach (explode(';', str_replace(' ', '', $operations)) as $operation) {
			if (empty($operation)) {
				continue;
			}

			if (strpos($operation, ':') === false) {
				$transform[] = $operation;
				continue;
			}

			if (!isset($width) || !isset($height)) {
				continue;
			}

			list($rules, $operation) = explode(':', $operation, 2);

			foreach (explode(',', $rules) as $rule) {
				$rule = explode('=', $rule, 2);
				$value = intval($rule[1]);

				switch ($rule[0]) {
					case 'max-width':
						if ($width > $value) {
							continue 2;
						}
						break;

					case 'min-width':
						if ($width < $value) {
							continue 2;
						}
						break;

					case 'width':
						if ($width != $value) {
							continue 2;
						}
						break;

					case 'max-height':
						if ($height > $value) {
							continue 2;
						}
						break;

					case 'min-height':
						if ($height < $value) {
							continue 2;
						}
						break;

					case 'height':
						if ($height != $value) {
							continue 2;
						}
						break;
				}

				$transform[] = $operation;
			}
		}

		return implode('|', $transform);
	}


	/**
	 * Gets the original image
	 *
	 * @return mixed  Depending of the library used: the Imagick instance, GD resource or null if no image has been loaded
	 */
	public function getImage () {
		return $this->image;
	}



	/**
	 * Gets the error in a ImageException instance
	 *
	 * @return ImageException object  The error exception or null if there is not errors
	 */
	public function getError () {
		return $this->Error;
	}



	/**
	 * Sets a new error for the image
	 *
	 * @param string  $message  The message of the error
	 * @param int     $code     The code of the message. It can be one of the following constants: IMAGECOW_ERROR_LOADING, IMAGECOW_ERROR_FUNCTION, IMAGECOW_ERROR_INPUT
	 *
	 * @return $this
	 */
	public function setError ($message = '', $code = null) {
		$this->Error = new ImageException($message, $code);

		return $this;
	}


	/**
	 * Define the image compression quality for jpg images
	 *
	 * @param int  $quality  The quality (from 0 to 100)
	 *
	 * @return $this
	 */
	public function setCompressionQuality ($quality) {
		$quality = intval($quality);

		if ($quality < 0) {
			$this->quality = 0;
		} else if ($quality > 100) {
			$this->quality = 100;
		} else {
			$this->quality = $quality;
		}

		return $this;
	}



	/**
	 * Reads the EXIF data from a JPEG and returns an associative array
	 *
	 * @return array The data where the array indexes are the header names and array values the associated values. Returns false on error
	 */
	public function getExifData () {
		$filename = $this->getFilename();

		return isset($filename) ? exif_read_data($filename) : false;
	}



	/**
	 * Transform the image executing various operations of crop, resize, resizeCrop and format
	 * 
	 * @param string  $operations  The string with all operations separated by "|".
	 *
	 * @return $this
	 */
	public function transform ($operations = '') {
		if (!$operations) {
			return $this;
		}

		$operations = $this->getOperations($operations);

		foreach ($operations as $operation) {
			call_user_func_array(array($this, $operation['function']), $operation['params']);
		}

		return $this;
	}



	/**
	 * Converts a string with operations in an array
	 *
	 * @param string  $operations  The operations string
	 *
	 * @return array  The operation width the function name and the parameters
	 */
	private function getOperations ($operations) {
		$valid_operations = array('resize', 'resizeCrop', 'crop', 'format');
		$operations = explode('|', str_replace(' ', '', $operations));
		$return = array();

		foreach ($operations as $operations) {
			$params = explode(',', $operations);
			$function = trim(array_shift($params));

			if (!in_array($function, $valid_operations)) {
				$this->setError('The transform function "'.$function.'" is not valid', IMAGECOW_ERROR_INPUT);
				continue;
			}

			$return[] = array(
				'function' => $function,
				'params' => $params
			);
		}

		return $return;
	}



	/**
	 * Send the HTTP header with the content-type, output the image data and die.
	 */
	public function show () {
		if (($string = $this->getString()) && ($mimetype = $this->getMimeType())) {
				header('Content-Type: '.$mimetype);
				die($string);
		}
	}



	/**
	 * Calculates the point position according with the image dimmensions.
	 *
	 * @param string/int  $position  The value of the position. It can be number (pixels), percentaje or one of the available keywords (top,left,bottom,right,middle,center)
	 * @param number      $size      The size of the new cropped/resized image.
	 * @param number      $canvas    The size of the old image
	 *
	 * @return integer The position of the point in pixeles.
	 */
	protected function position ($position, $size, $canvas) {
		if (is_int($position)) {
			return $position;
		}

		switch ($position) {
			case 'top':
			case 'left':
				$position = 0;
				break;

			case 'middle':
			case 'center':
				$position = ($canvas/2) - ($size/2);
				break;

			case 'right':
			case 'bottom':
				$position = $canvas - $size;
				break;

			default:
				$position = $this->getSize($position, $canvas);
		}

		return $position;
	}


	/**
	 * Adjust the image to the given dimmensions. Resizes and crops the image maintaining the proportions.
	 *
	 * @param int/string  $width   The new width in number (pixels) or percentaje
	 * @param int/string  $height  The new height in number (pixels) or percentaje
	 * @param int/string  $x       The "x" position where start to crop. It can be number (pixels), percentaje or one of the available keywords (left,center,right)
	 * @param int/string  $y       The "y" position where start to crop. It can be number (pixels), percentaje or one of the available keywords (top,middle,bottom)
	 *
	 * @return $this
	 */
	public function resizeCrop ($width, $height, $x = 'center', $y = 'middle') {
		$width = $this->getSize($width, $this->getWidth());
		$height = $this->getSize($height, $this->getHeight());

		if (($width === 0) || ($height === 0)) {
			return false;
		}

		$width_resize = ($width / $this->getWidth()) * 100;
		$height_resize = ($height / $this->getHeight()) * 100;

		if ($width_resize < $height_resize) {
			$this->resize(0, $height);
		} else {
			$this->resize($width, 0);
		}

		$this->crop($width, $height, $x, $y);

		return $this;
	}



	/**
	 * Get the size of the image.
	 *
	 * @param string/int  $value       The size in numbers (pixels) or percentaje.
	 * @param int         $total_size  The total size of the image (used to calculate the percentaje)
	 *
	 * @return integer  The calculated value
	 */
	protected function getSize ($value, $total_size) {
		if (substr($value, -1) === '%') {
			return ($total_size/100) * intval(substr($value, 0, -1));
		}

		return intval($value);
	}



	/**
	 * Check if the image must be enlarged or not (if the new dimmensions are bigger than original)
	 *
	 * @param int  $new_size       The new size of the image
	 * @param int  $original_size  The original size of the image
	 *
	 * @return boolean  True if the image must be enlarged and false if not.
	 */
	protected function enlarge ($new_size, $original_size) {
		if ($new_size && $new_size > $original_size) {
			return true;
		}

		return false;
	}
}
?>